﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="DeviceInfo.cs" company="Matthias Friedrich">
//   Copyright © Matthias Friedrich 2009
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace DeviceManagement
{
	using System;
	using System.Collections.Generic;
	using System.Runtime.InteropServices;

	public class DeviceInfo
	{
		private static readonly IDictionary<uint, Func<DevicePropertyKey, IntPtr, uint, IDeviceProperty>> PropertyHandlers = new Dictionary<uint, Func<DevicePropertyKey, IntPtr, uint, IDeviceProperty>>()
		{
			{ DeviceInstallation.DEVPROP_TYPE_NULL, (key, p, bufferLength) => new DeviceProperty(key, null) }, 
			{ DeviceInstallation.DEVPROP_TYPE_EMPTY, (key, p, bufferLength) => new DeviceProperty(key, null) }, 
			{ DeviceInstallation.DEVPROP_TYPE_BOOLEAN, (key, p, bufferLength) => new DeviceProperty(key, DevicePropertyHandlers.ParseBoolean(p, bufferLength)) }, 
			{ DeviceInstallation.DEVPROP_TYPE_UINT32, (key, p, bufferLength) => new DeviceProperty(key, DevicePropertyHandlers.ParseUInt32(p, bufferLength)) }, 
			{ DeviceInstallation.DEVPROP_TYPE_STRING, (key, p, bufferLength) => new DeviceProperty(key, DevicePropertyHandlers.ParseString(p, bufferLength)) }, 
			{ DeviceInstallation.DEVPROP_TYPE_STRING_LIST, (key, p, bufferLength) => new DeviceProperty(key, DevicePropertyHandlers.ParseStringList(p, bufferLength)) }, 
			{ DeviceInstallation.DEVPROP_TYPE_GUID, (key, p, bufferLength) => new DeviceProperty(key, DevicePropertyHandlers.ParseGuid(p, bufferLength)) }
		};

		internal readonly SP_DEVINFO_DATA DeviceInfoData;

		private readonly IntPtr deviceInfoSet;

		private readonly IDictionary<DevicePropertyKey, object> propertyCache = new Dictionary<DevicePropertyKey, object>();

		private readonly object syncObject = new object();

		internal DeviceInfo(IntPtr deviceInfoSet, SP_DEVINFO_DATA deviceInfoData)
		{
			if (deviceInfoSet == IntPtr.Zero)
			{
				throw new ArgumentException("The given device info set handle is invalid.", "deviceInfoSet");
			}

			this.deviceInfoSet = deviceInfoSet;

			this.DeviceInfoData = deviceInfoData;
		}

		public DeviceCaps Caps
		{
			get
			{
				return (DeviceCaps)this.TryGetProperty<uint>(DevicePropertyKeys.DEVPKEY_Device_Capabilities);
			}
		}

		public string ClassDescription
		{
			get
			{
				return DeviceInfoSet.GetClassDescription(this.ClassGuid);
			}
		}

		public Guid ClassGuid
		{
			get
			{
				return this.DeviceInfoData.ClassGuid;
			}
		}

		public string ClassName
		{
			get
			{
				return DeviceInfoSet.GetClassName(this.ClassGuid);
			}
		}

		public string Description
		{
			get
			{
				return this.TryGetProperty<string>(DevicePropertyKeys.DEVPKEY_Device_DeviceDesc);
			}
		}

		public uint DeviceInstanceId
		{
			get
			{
				return this.DeviceInfoData.DevInst;
			}
		}

		public string FriendlyName
		{
			get
			{
				return this.TryGetProperty<string>(DevicePropertyKeys.DEVPKEY_Device_FriendlyName);
			}
		}

		public string PhysicalObjectName
		{
			get
			{
				return this.TryGetProperty<string>(DevicePropertyKeys.DEVPKEY_Device_PDOName);
			}
		}

		public IDeviceProperty GetProperty(DevicePropertyKey property)
		{
			if (property == null)
			{
				throw new ArgumentNullException("property");
			}

			SP_DEVINFO_DATA deviceInfoData = this.DeviceInfoData;
			DEVPROPKEY propKey = property.PropKey;

			uint propertyType = 0;
			uint requiredSize = 0;

			DeviceInstallation.SetupDiGetDeviceProperty(this.deviceInfoSet, ref deviceInfoData, ref propKey, ref propertyType, IntPtr.Zero, 0, ref requiredSize, 0);

			uint propertyBufferSize = requiredSize;
			IntPtr pBuffer = Marshal.AllocHGlobal((int)propertyBufferSize);

			try
			{
				DeviceInstallation.SetupDiGetDeviceProperty(this.deviceInfoSet, ref deviceInfoData, ref propKey, ref propertyType, pBuffer, propertyBufferSize, ref requiredSize, 0);

				if (PropertyHandlers.ContainsKey(propertyType))
				{
					Func<DevicePropertyKey, IntPtr, uint, IDeviceProperty> handler = PropertyHandlers[propertyType];
					return handler(property, pBuffer, propertyBufferSize);
				}
			}
			finally
			{
				Marshal.FreeHGlobal(pBuffer);
			}

			return null;
		}

		public T TryGetProperty<T>(DevicePropertyKey property)
		{
			if (property == null)
			{
				throw new ArgumentNullException("property");
			}

			if (!this.propertyCache.ContainsKey(property))
			{
				lock (this.syncObject)
				{
					if (!this.propertyCache.ContainsKey(property))
					{
						IDeviceProperty deviceProperty;
						if ((deviceProperty = this.GetProperty(property)) != null)
						{
							if (deviceProperty.Value != null)
							{
								this.propertyCache[property] = deviceProperty.Value;
							}
						}
					}
				}
			}

			if (this.propertyCache.ContainsKey(property))
			{
				object value = this.propertyCache[property];

				return (T)value;
			}

			return default(T);
		}
	}
}